#PREVIOUS"

from math import log
from math import ceil
from copy import deepcopy
#from math import sin, cos
from math import *
from numpy import arange

FunctionPlotDefinition = 1000

class Problem:
    def __init__ (self, function_string, list_restrictions_strings, 
                  dict_limits, type, penalty_weight):
        self.my_problem=function_string
        self.my_restrictions=list_restrictions_strings
        self.my_limits=dict_limits
        self.my_type=type
        self.my_penalty_weight=penalty_weight
        self.my_vars_precisions=self.get_genesize_dictionary()
        self.recalculate_precicions()
    
    def recalculate_precicions(self):       
        bounds = self.my_limits
        genesize_dict = {}
        
        for var in bounds:
            vary=bounds[var]
            max = int(ceil((vary[1]-vary[0])/vary[2])+1)
            bits=int(ceil(log(max, 2)))
            new_precision = float((vary[1]-vary[0]))/float((2**bits)-1)
            vary[2] = new_precision
            
    def get_genesize_dictionary(self):
        """
            Returns a dictionary with the number of bits for each gene 
        
            .Parameters:
                                                
            .Return value:
        
                List with the number of bits for each gene
                
        """
        bounds = self.my_limits
        genesize_dict = {}
        genesize_dict=bounds.fromkeys(bounds)
        
        for var in bounds:
            vary=bounds[var]
            max = int(ceil((vary[1]-vary[0])/vary[2])+1)
            genesize_dict[var]=int(ceil(log(max, 2)))
            
        return genesize_dict
     
    def get_genesize_list(self):
        """
            Returns a list with the number of bits for each gene 
        
            .Parameters:
                                                
            .Return value:
        
                List with the number of bits for each gene
                
        """
        bounds = self.my_limits.values()
        genesize_list = []
        for var in bounds:
            max = int(ceil((var[1]-var[0])/var[2])+1)
            genesize_list.append(int(ceil(log(max, 2))))
            
        return genesize_list
       
    def eval(self, individual, *parameters):
        """

        Evaluate the given variable values, applying them both to the 
        function at hand and the restrictions defined as well
        
        .Parameters:

              parameters:    Can contain number of generations
                                                
        .Return value:
        
              Result of Evaluation:    Tuple with (Output of function, 
                                                number of penalties)
        """
        
        generations=parameters[0]
        dictionary_of_variables=self.transform_genotype_fenotype(individual)

        penalties=0
        for i in range(0, len(self.my_restrictions)):
            if(not eval(self.my_restrictions[i], dictionary_of_variables)):
                penalties+=1

        if(self.my_type=="Maximization"):
            penalties=penalties*-1;                
        try:
            evaluation=(eval(self.my_problem, globals(), dictionary_of_variables)+ \
                       (float(self.my_penalty_weight)*float(generations+1)* \
                       (float(penalties))))

        except Exception:
            #In case its not a valid operation
            print "---EVAL ERROR---"
            evaluation = 0

        if(self.my_type=="Minimization"):
            return 0-evaluation;
        else:
            return evaluation
        
    def eval_graph(self, individual, *parameters):
        """

        Evaluate the given variable values, applying them both to the 
        function at hand and the restrictions defined as well
        
        .Parameters:

              parameters:    Can contain number of generations
                                                
        .Return value:
        
              Result of Evaluation:    Tuple with (Output of function, 
                                                number of penalties)
        """
        
        generations=parameters[0]
        dictionary_of_variables=self.transform_genotype_fenotype(individual)

        try:
            evaluation=(eval(self.my_problem, globals(), dictionary_of_variables))
        except Exception:
            #In case its not a valid operation
            print "---EVAL ERROR---"
            evaluation = 0
      
        return evaluation
    
    def create_function(self, pop):
        
        function_data = []
        population_data = []
        
        lims = self.my_limits.values()
        keys = self.my_limits.keys()
        if(len(keys)==1):
            for i in arange(lims[0][0], lims[0][1],(lims[0][1]-lims[0][0])/FunctionPlotDefinition):
                function_data += [ [i, eval(self.my_problem,globals(),dict([[keys[0], i]]) ) ] ]
            
            for i in range(0, len(pop)):
                population_data += [[ self.transform_genotype_fenotype(pop[i])[keys[0]], self.eval_graph(pop[i], 0) ] ]
               
            return [function_data, population_data]
        
        return [None, None]
    
    def calculate_distance(self, variable, value):
        
        precision=self.my_vars_precisions[variable]
        total=((2**precision)+1)*self.my_limits[variable][2]+self.my_limits[variable][0]
        distance=((value-self.my_limits[variable][1])**2)/((total-self.my_limits[variable][1])**2)

        return distance
    
    def transform_genotype_fenotype(self, genotype):
        """

        Transform binary values in float values
        
        .Parameters:
        
            genotype:    List of genes with binary cromossome
                                                
        .Return value:
        
            fenotype:    Dictionary with values of variables
            
        """
        fenotype_values = []
        bounds = self.my_limits.values()
        vars = self.my_limits.keys()
        
        for i in range(0,len(self.my_limits)):
            fenotype_values.append(self.gbinary_to_float(genotype[i], bounds[i]))

        fenotype = dict(map((lambda x,y: [x,y]),vars,fenotype_values))
        return fenotype
    
    def transform_fenotype_genotype(self, fenotype):
        
        """

        Transform binary values in float values
        
        .Parameters:
        
            fenotype:    Dictionary of variables with respective values
                                                
        .Return value:
        
            genotype:    List of binary values
            
        """
        
        fenotype_vals= fenotype.values()
        bounds = self.my_limits.values()
        
        genotype = []
        for i in range(0,len(fenotype)):
            genotype.append(self.float_to_gbinary(fenotype_vals[i], bounds[i]))
        
        return genotype
    
    def gbinary_to_float(self, gray_binary_val, bounds):
    
        """

        Convert gray binary values to float values
        
        .Parameters:
        
            gray_binary_val:    List of gray binary values
            bounds:             List with the lower [0] and upper [1] value of the variable  
                                and the corresponding precision [2]
        .Return value:
        
            f_val:              Float value of the list
            
        """
        integer_val = 0
        real_binary = self.convert_gray_to_binary(gray_binary_val)

        for i in range(0, len(real_binary)):
            integer_val = (2**i)*real_binary[len(real_binary)-i-1] + integer_val

        f_val = bounds[0]+integer_val*bounds[2]
        
        return f_val
    
    def float_to_gbinary(self, float_val, bounds):
    
        """

        Convert float value to gray binary value
        
        .Parameters:
        
            f_val:              Float number
            bounds:             List with the lower [0] and upper [1] value of the variable
                                and the corresponding precision [2]

        .Return value:
        
            g_val:              List of gray binary values
            
        """
    
        max = int(round(ceil((bounds[1] - bounds[0])/bounds[2])))+1		
        
        num_bits =int(ceil(log(max, 2)))
    
        integer_value = int((float_val-bounds[0])/bounds[2])
    
        """code by: Anton Vredegoor anton.vredegoor at gmail.com START"""
        b_val = [(0,1)[integer_value>>j & 1] for j in xrange(num_bits-1,-1,-1)]
        """code by: Anton Vredegoor anton.vredegoor at gmail.com STOP"""
        
        g_val = self.convert_binary_to_gray(b_val)

        return g_val
    
    def convert_binary_to_gray(self, binary):
        
        """
        Convert binary value to gray binary
        
        .Parameters:
        
            binary:            list of binary value

        .Return value:
        
            gray:              equivalent list in gray binary
            
        """
        
        gray = deepcopy(binary)
        
        for i in range(0,len(binary)-1):
            gray[i+1]=binary[i+1] ^ binary[i]
        
        return gray
    
    def convert_gray_to_binary(self, gray):
        """
        Convert gray binary to binary
        
        .Parameters:
        
            gray:              equivalent list in gray binary

        .Return value:
    
            binary:            list of binary value
        """
        binary = deepcopy(gray)
        
        for i in range(0,len(gray)-1):
            binary[i+1]= gray[i+1] ^ binary[i]
        
        return binary
    
    def bits(i,n):
        return [(0,1)[i>>j & 1] for j in xrange(n-1,-1,-1)]
